﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace Mindfor
{
	/// <summary>
	/// Class extends <see cref="System.Drawing.Image"/>.
	/// </summary>
	public static class ImageExtensions
	{
		/// <summary>
		/// Adjusts image size and dpi. Width and height proportion does not changes.
		/// </summary>
		/// <param name="image">Image to adjust.</param>
		/// <param name="maxWidth">Max image width. Use -1 not to analyse width.</param>
		/// <param name="maxHeight">Max image height. Use -1 not to analyse height.</param>
		/// <param name="dpi">Image new DPI. Use -1 not to change DPI.</param>
		public static Image AdjustImage(this Image image, int maxWidth, int maxHeight, float dpi)
		{
			if (image == null)
				return null;

			Bitmap bitmap = new Bitmap(image);

			// Изменить DPI, если нужно
			if (dpi > 0 && (bitmap.HorizontalResolution > dpi || bitmap.VerticalResolution > dpi))
				bitmap.SetResolution(dpi, dpi);

			// Определить нужные размеры картинки
			int x = bitmap.Width;
			int y = bitmap.Height;
			if (maxWidth > 0 && x > maxWidth)
			{
				y = (int)((double)y * (double)maxWidth / (double)x);
				x = maxWidth;
			}
			if (maxHeight > 0 && y > maxHeight)
			{
				x = (int)((double)x * (double)maxHeight / (double)y);
				y = maxHeight;
			}

			// Изменить размеры картинки, если требуется
			if (x != bitmap.Width || y != bitmap.Height)
				return bitmap.GetThumbnailImage(x, y, new Image.GetThumbnailImageAbort(delegate() { return true; }), IntPtr.Zero);
			else
				return bitmap;
		}

		/// <summary>
		/// Adjusts image size and dpi. Width and height proportion does not changes.
		/// </summary>
		/// <param name="image">Image to adjust.</param>
		/// <param name="maxWidth">Max image width. Use -1 not to analyse width.</param>
		/// <param name="maxHeight">Max image height. Use -1 not to analyse height.</param>
		public static Image AdjustGifImage(this Image image, int maxWidth, int maxHeight)
		{
			image.SelectActiveFrame(FrameDimension.Page, 0);
			return image.AdjustImage(maxWidth, maxHeight, -1);
		}

		/// <summary>
		/// Saves image to bytes array.
		/// </summary>
		/// <param name="image">Image to save.</param>
		/// <param name="format">Image format.</param>
		/// <param name="quality">Image quality percents (0-100).</param>
		/// <returns>Image bytes array.</returns>
		public static byte[] SaveImage(this Image image, ImageFormat format, int quality)
		{
			// Проверяем качество
			quality = Math.Min(quality, 100);
			quality = Math.Max(quality, 0);

			// Предварительно сохраняем в память ms, потому что
			// для некоторых форматов необходима поддержка 
			// абсолютного позиционирования указателя чтения/записи в потоке.
			using (MemoryStream ms = new MemoryStream())
			{
				bool saved = false;

				// Если jpeg, то сохраняем специально
				// TODO: ПОКА ЭТО НЕ РАБОТАЕТ!
				//if (format == ImageFormat.Jpeg)
				//{
				//    // Настраиваем качество
				//    EncoderParameters jpegParams = new EncoderParameters(1);
				//    jpegParams.Param[0] = new EncoderParameter(Encoder.Quality, (byte)quality);

				//    // Ищем нужный кодек, чтобы указать ему качество записи
				//    var encoders = ImageCodecInfo.GetImageEncoders();
				//    foreach (var encoder in encoders)
				//    {
				//        if (encoder.MimeType == "image/jpeg")
				//        {
				//            image.Save(ms, encoder, jpegParams);
				//            saved = true;
				//            break;
				//        }
				//    }					
				//}

				// Если не нашли, то используем стандартную функцию сохранения картинки
				if (!saved)
					image.Save(ms, format);


				// Из памяти сохраняем в однонаправленный поток.
				ms.Flush();
				return ms.GetBuffer();
			}
		}

		/// <summary>
		/// Saves image to stream.
		/// </summary>
		/// <param name="image">Image to save.</param>
		/// <param name="stream">Output stream.</param>
		/// <param name="format">Image format.</param>
		/// <param name="quality">Image quality percents (0-100).</param>
		public static void SaveImage(this Image image, Stream stream, ImageFormat format, int quality)
		{
			byte[] bytes = SaveImage(image, format, quality);
			stream.Write(bytes, 0, bytes.Length);
		}

		/// <summary>
		/// Draws specified part of original image into new Binary object.
		/// </summary>
		/// <param name="image">Source image</param>
		/// <param name="width">Result image width</param>
		/// <param name="height">Resul image height</param>
		/// <param name="x">X coordinate of upper-left corner of source rectangle</param>
		/// <param name="y">Y coordinate of upper-left corner of source rectangle</param>
		/// <returns>Image saved to Binary object</returns>
		public static Binary Crop(this Binary image, int width, int height, int x, int y)
		{
			byte[] initialBytes = image.ToArray();
			using (MemoryStream initialImageStream = new MemoryStream(initialBytes))
			using (Bitmap initialImage = new Bitmap(initialImageStream))
			using (MemoryStream resStream = new MemoryStream())
			{
				Image result = initialImage.Crop(width, height, x, y);
				result.SaveImage(resStream, ImageFormat.Png, 100);
				return new Binary(resStream.ToArray());
			}
		}

		/// <summary>
		/// Draws specified part of original image into new Binary object.
		/// </summary>
		/// <param name="image">Source image</param>
		/// <param name="width">Result image width</param>
		/// <param name="height">Resul image height</param>
		/// <param name="x">X coordinate of upper-left corner of source rectangle</param>
		/// <param name="y">Y coordinate of upper-left corner of source rectangle</param>
		/// <param name="format">Image format</param>
		/// <returns>Image saved to Binary object</returns>
		public static Binary Crop(this Binary image, int width, int height, int x, int y, ImageFormat format)
		{
			byte[] initialBytes = image.ToArray();
			using (MemoryStream initialImageStream = new MemoryStream(initialBytes))
			using (Bitmap initialImage = new Bitmap(initialImageStream))
			using (MemoryStream resStream = new MemoryStream())
			{
				Image result = initialImage.Crop(width, height, x, y);
				result.SaveImage(resStream, format, 100);
				return new Binary(resStream.ToArray());
			}
		}

		/// <summary>
		/// Draws specified part of original image.
		/// </summary>
		/// <param name="image">Source image</param>
		/// <param name="width">Result image width</param>
		/// <param name="height">Resul image height</param>
		/// <param name="x">X coordinate of upper-left corner of source rectangle</param>
		/// <param name="y">Y coordinate of upper-left corner of source rectangle</param>
		/// <returns>Cropped image (or enlarged if is less then needed size).</returns>
		public static Image Crop(this Image image, int width, int height, int x, int y)
		{
			Image bmp = new Bitmap(width, height);
			
			// set graphics
			Graphics gfx = Graphics.FromImage(bmp);
			gfx.SmoothingMode = SmoothingMode.AntiAlias;
			gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
			gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;

			// draw image (enlarge if less or crop if larger)
			gfx.DrawImage(image, new Rectangle(0, 0, width, height), x, y, width, height, GraphicsUnit.Pixel);

			// dispose to free up resources
			gfx.Dispose();
			return bmp;
		}
	}
}
